<?php

class AttachementList
{
    private $mailId;
    private $sizeLimit;
    private $attachments = array();

    public function __construct($mailId, $limit = 0)
    {
        if (empty($mailId))
        {
            throw new LogicException("Mail id can't be empty.");
        }
        $this->mailId = $mailId;
        $this->sizeLimit = $limit;
        $this->Update();
        $this->PrepareMailDir();
    }

    /**
     * Returns true if a file of the size $size can be added to the list.
     *
     * @param int $size
     *
     * @return bool
     */
    public function CheckLimit($size = 0)
    {
        $result = true;
        if ($this->sizeLimit)
        {
            $totalSize = $this->GetSizeFiles();
            $result = $totalSize + $size <= $this->sizeLimit;
        }
        return $result;
    }

    /**
     * Returns true if attachement exists.
     *
     * @param bool $key
     */
    public function AttachementExists($key)
    {
        return isset($this->attachments[$key]);
    }

    /**
     * Returns true if file exists.
     *
     * @param bool $fileName
     */
    public function FileExists($fileName)
    {
        foreach ($this->attachments as $attachment)
        {
            if ($attachment['name'] == $fileName)
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Removes attachement by key.
     *
     * @param string $key
     */
    public function RemoveAttachement($key)
    {
        Core::DBConn()->Query('DELETE FROM `message_attachments` WHERE `message_uid` = @uid AND `key` = @key',
            array(
                '@uid' => $this->mailId,
                '@key' => $key
            )
        );

        if ($this->AttachementExists($key))
        {
            unlink($this->GetAttachementPath($key));
        }
    }

    /**
     * Returns attachement path by key.
     *
     * @param string $key
     */
    public function GetAttachementPath($key)
    {
        return $this->GetMailDirPath() . '/' . $key;
    }

    /**
     * Returns attachement url.
     *
     * @param string $key
     * @return string
     */
    public function GetAttachementUrl($key)
    {
        return "/mail/attachements/{$this->mailId}/{$key}";
    }

    /**
     * Returns delete attachment url
     *
     * @param string $key
     */
    public function GetDeleteAttachmentUrl($key)
    {
        return "/account/attachement-list.html?rm_file={$key}&mail_id={$this->mailId}";
    }

    /**
     * Returns attachements list as array.
     *
     * @return array
     */
    public function GetAsArray()
    {
        $attachementList = array();

        foreach ($this->attachments as $key => $attachment)
        {
            $attachementList[] = $this->GetFileByKey($key);
        }

        return $attachementList;
    }

    /**
     * @param string $key
     */
    public function GetFileByKey($key)
    {
        if ($this->AttachementExists($key))
        {
            $attachment = $this->attachments[$key];

            return array(
                "fileName"  => $attachment['name'],
                "path"      => $this->GetAttachementPath($key),
                "url"       => $this->GetAttachementUrl($key),
                "size"      => $attachment['size'],
                "type"      => $attachment['type'],
                "deleteUrl" => $this->GetDeleteAttachmentUrl($key)
            );
        }

        return null;
    }

    /**
     * Add attachement from $_FILES array.
     *
     * @return AttachmentUploadStatus::*
     */
    public function AddAttachementFromFiles()
    {
        $files = $_FILES;
        if (!isset($files['Filedata']))
            return AttachmentUploadStatus::OK;

        $attachment = $files['Filedata'];
        if (($error = $this->checkFileError($files['Filedata'])) !== AttachmentUploadStatus::OK)
        {
            return $error;
        }

        return $this->CreateAttachment($attachment);
    }

    /**
     * Removes all attachements of mail.
     */
    public function RemoveAttachementList()
    {
        foreach ($this->attachments as $key => $attachment)
        {
            $this->RemoveAttachement($key);
        }
        $this->RemoveMailDir();
    }

    /**
     * Removes mail dir.
     */
    public function RemoveMailDir()
    {
        $mailDirPath = $this->GetMailDirPath();
        if (file_exists($mailDirPath))
        {
            rmdir($mailDirPath);
        }
    }

    /**
     * Returns files array for post request.
     *
     * @return array
     */
    public function GetFilesForPost()
    {
        $files = array();

        foreach ($this->attachments as $key => $attachment)
        {
            $files[] = array(
                'name' => $attachment['name'],
                'type' => $attachment['type'],
                'path' => $this->GetAttachementPath($key)
            );
        }

        return $files;
    }

    /**
     * Reload attachments list from database.
     */
    public function Update()
    {
        $sql = "SELECT * FROM `message_attachments` WHERE `message_uid` = %s";
        $data = Core::DBConn()->Query( $sql, Util::Quote($this->mailId) )->GetAllAssoc();

        if ( !empty( $data ) )
        {
            $this->attachments = array();
            foreach ($data as $attachment)
            {
                $this->attachments[$attachment['key']] = array(
                    'name' => $attachment['original_name'],
                    'type' => $attachment['mime_type'],
                    'size' => $attachment['size']
                );
            }
        }
    }

    /**
     * @param array(name, type, tmp_name, error, size) $attachment
     * @return AttachmentUploadStatus::*
     */
    public function CreateAttachment($attachment)
    {
        if ($this->FileExists($attachment['name']))
        {
            return AttachmentUploadStatus::ALREADY_EXISTS;
        }

        if (!$this->CheckLimit($attachment['size']))
        {
            return AttachmentUploadStatus::LIMIT_EXCEEDED;
        }

        // attachments quick fix.
        $key = self::GenerateKey();
        if (!$this->MoveFileToMailDir($key, $attachment['tmp_name']))
        {
            return AttachmentUploadStatus::ERROR;
        }

        $sql = 'INSERT INTO message_attachments
                                (`key`, `message_uid`, `mime_type`, `original_name`, `size`)
                            VALUE
                                (@key, @uid, @type, @name, @size)';

        $params = array
        (
            '@key'  => $key,
            '@uid'  => $this->mailId,
            '@type' => $attachment['type'],
            '@name' => $attachment['name'],
            '@size' => $attachment['size']
        );

        Core::DBConn()->Query( $sql, Util::QuoteArray($params) );
        $this->Update();

        return AttachmentUploadStatus::OK;
    }

    /**
     * Total size of files.
     *
     * @return int
     */
    public function GetSizeFiles()
    {
        $size = 0;
        foreach ($this->attachments as $key => $attachment)
        {
            $size += intval($attachment['size']);
        }
        return $size;
    }

    /**
     * @param string $fileName
     * @param string $filePath
     * @return bool
     */
    private function MoveFileToMailDir($fileName, $filePath)
    {
        $attachementPath = $this->GetAttachementPath($fileName);
        if ($this->FileExists($fileName))
        {
            return false;
        }
        $fileContent = file_get_contents($filePath);
        file_put_contents($attachementPath, $fileContent);

        return true;
    }

    /**
     * @return string
     */
    private static function GenerateKey()
    {
        return KeyGenerator::generateSolidKey();
    }

    /**
     * Prepares mail directory. Creates it's if it's not exists.
     */
    private function PrepareMailDir()
    {
        $mailDirPath = $this->GetMailDirPath();
        if (!file_exists($mailDirPath))
        {
            mkdir($mailDirPath, 0777);
        }
    }

    /**
     * Returns mail dir path.
     *
     * @return string
     */
    private function GetMailDirPath()
    {
        return ROOT_PATH . "upload/mail/" . $this->mailId ;
    }

    /**
     * Checks the error code from a file info array and
     * returns the corresponding upload status.
     *
     * @param $fileInfo
     * @return AttachmentUploadStatus::*
     */
    private function CheckFileError($fileInfo)
    {
        $result = AttachmentUploadStatus::OK;
        if (isset($fileInfo['error']))
        {
            $result = AttachmentUploadStatus::getByUploadError($fileInfo['error']);
        }
        return $result;
    }
}